#pragma once

#include <algorithm> // For std::copy, std::search
#include <cassert>
#include <cstdint>   // For uint16_t, uint32_t etc.
#include <cstring>   // For memcpy, memchr
#include <stdexcept> // For std::out_of_range
#include <string>
#include <string_view> // C++17
#include <vector>

// For readFd (POSIX specific) - simplified version
#include <errno.h>  // For errno
#include <unistd.h> // For read, ssize_t
// #include <sys/uio.h> // For iovec, readv (original muduo used this)

namespace sola // Using a new namespace
{

/// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer
///
/// @code
/// +-------------------+------------------+------------------+
/// | prependable bytes |  readable bytes  |  writable bytes  |
/// |                   |     (CONTENT)    |                  |
/// +-------------------+------------------+------------------+
/// |                   |                  |                  |
/// 0      <=      readerIndex_   <=   writerIndex_    <=     size
/// @endcode
class Buffer {
public:
    static inline const size_t kCheapPrepend = 8;    // Space before actual content, for prepending headers
    static inline const size_t kInitialSize  = 1024; // Initial size of the writable part

    explicit Buffer(size_t initialSize = kInitialSize)
        : buffer_(kCheapPrepend + initialSize)
        , readerIndex_(kCheapPrepend)
        , writerIndex_(kCheapPrepend) {
        assert(readableBytes() == 0);
        assert(writableBytes() == initialSize);
        assert(prependableBytes() == kCheapPrepend);
    }

    // Default copy-ctor, move-ctor, dtor and assignment are fine for std::vector

    void swap(Buffer& rhs) noexcept {
        buffer_.swap(rhs.buffer_);
        std::swap(readerIndex_, rhs.readerIndex_);
        std::swap(writerIndex_, rhs.writerIndex_);
    }

    size_t readableBytes() const noexcept { return writerIndex_ - readerIndex_; }

    size_t writableBytes() const noexcept { return buffer_.size() - writerIndex_; }

    size_t prependableBytes() const noexcept { return readerIndex_; }

    // Pointer to the beginning of readable data
    const char* peek() const noexcept { return begin() + readerIndex_; }

    // Pointer to the beginning of writable data
    char* beginWrite() noexcept { return begin() + writerIndex_; }

    const char* beginWrite() const noexcept { return begin() + writerIndex_; }

    // --- Retrieve Operations ---

    void retrieve(size_t len) {
        if (len == 0)
            return;
        if (len < readableBytes()) {
            readerIndex_ += len;
        } else {
            retrieveAll(); // len >= readableBytes()
        }
        assert(readerIndex_ <= writerIndex_);
    }

    void retrieveUntil(const char* end) {
        assert(peek() <= end);
        assert(end <= beginWrite());
        retrieve(static_cast<size_t>(end - peek()));
    }

    void retrieveAll() noexcept {
        readerIndex_ = kCheapPrepend;
        writerIndex_ = kCheapPrepend;
    }

    std::string retrieveAsString(size_t len) {
        if (len > readableBytes()) {
            // Or silently truncate: len = readableBytes();
            throw std::out_of_range("retrieveAsString: len exceeds readable bytes");
        }
        std::string result(peek(), len);
        retrieve(len);
        return result;
    }

    std::string retrieveAllAsString() { return retrieveAsString(readableBytes()); }

    // --- Append Operations ---

    void append(const char* data, size_t len) {
        if (data == nullptr || len == 0)
            return;
        ensureWritableBytes(len);
        std::copy(data, data + len, beginWrite());
        hasWritten(len);
    }

    void append(const void* data, size_t len) { append(static_cast<const char*>(data), len); }

    void append(const std::string& str) { append(str.data(), str.size()); }

    void append(std::string_view sv) // C++17
    {
        append(sv.data(), sv.size());
    }

    // Append operations for basic types (host byte order)
    void appendInt8(int8_t x) { append(&x, sizeof(x)); }
    void appendUint8(uint8_t x) { append(&x, sizeof(x)); }
    void appendInt16(int16_t x) { append(&x, sizeof(x)); }
    void appendUint16(uint16_t x) { append(&x, sizeof(x)); }
    void appendInt32(int32_t x) { append(&x, sizeof(x)); }
    void appendUint32(uint32_t x) { append(&x, sizeof(x)); }
    void appendInt64(int64_t x) { append(&x, sizeof(x)); }
    void appendUint64(uint64_t x) { append(&x, sizeof(x)); }

    // --- Peek Operations for basic types (host byte order) ---
private:
    template<typename T> T peekInteger() const {
        if (readableBytes() < sizeof(T)) {
            throw std::out_of_range("peekInteger: not enough data for type");
        }
        T val;
        std::memcpy(&val, peek(), sizeof(T));
        return val;
    }

public:
    int8_t   peekInt8() const { return peekInteger<int8_t>(); }
    uint8_t  peekUint8() const { return peekInteger<uint8_t>(); }
    int16_t  peekInt16() const { return peekInteger<int16_t>(); }
    uint16_t peekUint16() const { return peekInteger<uint16_t>(); }
    int32_t  peekInt32() const { return peekInteger<int32_t>(); }
    uint32_t peekUint32() const { return peekInteger<uint32_t>(); }
    int64_t  peekInt64() const { return peekInteger<int64_t>(); }
    uint64_t peekUint64() const { return peekInteger<uint64_t>(); }

    // --- Retrieve Operations for basic types (host byte order) ---
private:
    template<typename T> T retrieveInteger() {
        T val = peekInteger<T>();
        retrieve(sizeof(T));
        return val;
    }

public:
    int8_t   retrieveInt8() { return retrieveInteger<int8_t>(); }
    uint8_t  retrieveUint8() { return retrieveInteger<uint8_t>(); }
    int16_t  retrieveInt16() { return retrieveInteger<int16_t>(); }
    uint16_t retrieveUint16() { return retrieveInteger<uint16_t>(); }
    int32_t  retrieveInt32() { return retrieveInteger<int32_t>(); }
    uint32_t retrieveUint32() { return retrieveInteger<uint32_t>(); }
    int64_t  retrieveInt64() { return retrieveInteger<int64_t>(); }
    uint64_t retrieveUint64() { return retrieveInteger<uint64_t>(); }

    // --- Prepend Operations (for adding headers, etc.) ---
    void prepend(const void* data, size_t len) {
        if (data == nullptr || len == 0)
            return;
        if (len > prependableBytes()) {
            throw std::out_of_range("prepend: len exceeds prependableBytes");
        }
        readerIndex_ -= len;
        const char* char_data = static_cast<const char*>(data);
        std::copy(char_data, char_data + len, begin() + readerIndex_);
    }

    void prependInt8(int8_t x) { prepend(&x, sizeof(x)); }
    void prependUint8(uint8_t x) { prepend(&x, sizeof(x)); }
    void prependInt16(int16_t x) { prepend(&x, sizeof(x)); }
    void prependUint16(uint16_t x) { prepend(&x, sizeof(x)); }
    void prependInt32(int32_t x) { prepend(&x, sizeof(x)); }
    void prependUint32(uint32_t x) { prepend(&x, sizeof(x)); }
    void prependInt64(int64_t x) { prepend(&x, sizeof(x)); }
    void prependUint64(uint64_t x) { prepend(&x, sizeof(x)); }

    // --- Find Operations ---
    const char* findEOL() const // Finds '\n'
    {
        const void* eol = std::memchr(peek(), '\n', readableBytes());
        return static_cast<const char*>(eol);
    }

    // Finds '\n' starting from 'start'
    const char* findEOL(const char* start) const {
        if (start == nullptr || start < peek() || start >= beginWrite()) {
            assert(false && "findEOL: invalid start pointer");
            return nullptr;
        }
        const void* eol = std::memchr(start, '\n', static_cast<size_t>(beginWrite() - start));
        return static_cast<const char*>(eol);
    }

    // Finds "\r\n"
    const char* findCRLF() const {
        const char  pattern[] = "\r\n";
        const char* crlf      = std::search(peek(),
                                       beginWrite(),
                                       std::begin(pattern),
                                       std::end(pattern) - 1); // -1 because pattern includes null terminator
        return (crlf == beginWrite()) ? nullptr : crlf;
    }

    // Finds "\r\n" starting from 'start'
    const char* findCRLF(const char* start) const {
        if (start == nullptr || start < peek() || start >= beginWrite()) {
            assert(false && "findCRLF: invalid start pointer");
            return nullptr;
        }
        const char  pattern[] = "\r\n";
        const char* crlf      = std::search(start, beginWrite(), std::begin(pattern), std::end(pattern) - 1);
        return (crlf == beginWrite()) ? nullptr : crlf;
    }

    // --- Buffer Management ---
    void ensureWritableBytes(size_t len) {
        if (writableBytes() < len) {
            makeSpace(len);
        }
        assert(writableBytes() >= len);
    }

    void hasWritten(size_t len) noexcept {
        assert(len <= writableBytes());
        writerIndex_ += len;
    }

    void unwrite(size_t len) // "Undoes" a write, moves writerIndex_ back
    {
        if (len > readableBytes()) { // Cannot unwrite more than what's readable
            throw std::out_of_range("unwrite: len exceeds readableBytes");
        }
        writerIndex_ -= len;
        assert(writerIndex_ >= readerIndex_);
    }

    // Shrinks the internal vector to fit the current content + reserve.
    // Uses shrink_to_fit if available (C++11).
    void shrink(size_t reserve) {
        if (buffer_.capacity() > readableBytes() + reserve + kCheapPrepend) {
            Buffer other(readableBytes() + reserve); // New buffer with desired capacity
            other.append(peek(), readableBytes());   // Copy content
            swap(other);                             // Swap with current
            // In C++11 and later, vector::shrink_to_fit() is preferred
            // but the copy-and-swap idiom is a common pre-C++11 way.
            // Or simply: buffer_.shrink_to_fit(); if you know you have C++11
        }
    }

    size_t internalCapacity() const noexcept { return buffer_.capacity(); }

    // --- Read data directly into buffer from a file descriptor ---
    // Simplified version using ::read.
    // Muduo's version uses readv for potential optimization.
    ssize_t readFd(int fd, int* savedErrno) {
        // Save some space in extrabuf in case an event is triggered and
        // buffer_ is not large enough
        char extrabuf[65536]; // 64KB, fairly large for typical reads

        // Try to read directly into our buffer's writable space first
        size_t writable = writableBytes();
        if (writable == 0) {                   // No space in current buffer, need to make some or use extrabuf
            ensureWritableBytes(kInitialSize); // Ensure at least some space
            writable = writableBytes();
        }

        ssize_t n = ::read(fd, beginWrite(), writable);
        if (n < 0) {
            if (savedErrno)
                *savedErrno = errno;
            return n;
        } else if (n > 0) {
            writerIndex_ += static_cast<size_t>(n);
            return n;
        }

        // n == 0 (EOF) or if somehow read returned 0 but no error
        // If we want to try reading more using extrabuf (like muduo's readv logic)
        // This part makes it more complex. For a simpler version, we can stop here.
        // For a slightly more robust version (if n was less than writable, or if n was 0 but more data might be
        // available): Muduo's readv logic is designed to fill the primary buffer first, then an extra buffer. A
        // simplified ::read() cannot easily do that in one call if the primary buffer is small. Let's keep it simple:
        // one read attempt into our writable space. If more advanced scatter-gather is needed, then SocketsOps::readv
        // and iovec are the way.

        // If the first read filled up the writable space, and we want to read more (like muduo):
        // This means n == writable.
        // We could then try to read into extrabuf.
        if (static_cast<size_t>(n) == writable && writable > 0) {
            ssize_t n2 = ::read(fd, extrabuf, sizeof(extrabuf));
            if (n2 > 0) {
                append(extrabuf, static_cast<size_t>(n2));
                return n + n2; // Total bytes read
            } else if (n2 < 0) {
                if (savedErrno)
                    *savedErrno = errno;
                // If the first read was successful, but the second failed,
                // we still return the bytes from the first read.
                // The error from the second read is stored in savedErrno.
                // This behavior might need adjustment based on desired error handling.
                return n;
            }
            // if n2 == 0, just return n
        }
        return n;
    }

private:
    char* begin() noexcept // Pointer to the beginning of the internal_ vector
    {
        return &*buffer_.begin();
    }

    const char* begin() const noexcept { return &*buffer_.begin(); }

    void makeSpace(size_t len) {
        // If total free space (prependable + writable) is not enough
        if (writableBytes() + prependableBytes() < len + kCheapPrepend) {
            buffer_.resize(writerIndex_ + len);
        } else // Enough free space, just move readable data to the front
        {
            assert(kCheapPrepend < readerIndex_); // Should have some prependable space to move into
            size_t readable = readableBytes();
            std::copy(begin() + readerIndex_, begin() + writerIndex_, begin() + kCheapPrepend);
            readerIndex_ = kCheapPrepend;
            writerIndex_ = readerIndex_ + readable;
            assert(readable == readableBytes());
            assert(writableBytes() >= len);
        }
    }

private:
    std::vector<char> buffer_;
    size_t            readerIndex_;
    size_t            writerIndex_;

    // static const char kCRLF[]; // Muduo defines this in .cc file
    // For single header, define it here if needed, or use literal in findCRLF
};

// const char Buffer::kCRLF[] = "\r\n"; // If you need it as a static member

// Helper for swapping
inline void swap(Buffer& a, Buffer& b) noexcept {
    a.swap(b);
}

} // namespace sola
